<?php

namespace Aspic;

use Aspic\Security\Security as S;

class UString {

	const DIRECTORY_SEPARATOR = '/';
	const POS_END = 'posEnd';

	public static function insert($strIns, $pos, $str) {
		return substr_replace($str, $strIns, $pos, 0);
	}

	/**
	 * 
	 * @param string $str
	 * @return string
	 * @todo Use viva version
	 */
	public static function replaceLettersSpecialChars($str) {
		$unwanted_array = array('Š' => 'S', 'š' => 's', 'Ž' => 'Z', 'ž' => 'z', 'À' => 'A', 'Á' => 'A', 'Â' => 'A', 'Ã' => 'A', 'Ä' => 'A', 'Å' => 'A', 'Æ' => 'A', 'Ç' => 'C', 'È' => 'E', 'É' => 'E',
			'Ê' => 'E', 'Ë' => 'E', 'Ì' => 'I', 'Í' => 'I', 'Î' => 'I', 'Ï' => 'I', 'Ñ' => 'N', 'Ò' => 'O', 'Ó' => 'O', 'Ô' => 'O', 'Õ' => 'O', 'Ö' => 'O', 'Ø' => 'O', 'Ù' => 'U',
			'Ú' => 'U', 'Û' => 'U', 'Ü' => 'U', 'Ý' => 'Y', 'Þ' => 'B', 'ß' => 'Ss', 'à' => 'a', 'á' => 'a', 'â' => 'a', 'ã' => 'a', 'ä' => 'a', 'å' => 'a', 'æ' => 'a', 'ç' => 'c',
			'è' => 'e', 'é' => 'e', 'ê' => 'e', 'ë' => 'e', 'ì' => 'i', 'í' => 'i', 'î' => 'i', 'ï' => 'i', 'ð' => 'o', 'ñ' => 'n', 'ò' => 'o', 'ó' => 'o', 'ô' => 'o', 'õ' => 'o',
			'ö' => 'o', 'ø' => 'o', 'ù' => 'u', 'ú' => 'u', 'û' => 'u', 'ü' => 'u', 'ý' => 'y', 'ý' => 'y', 'þ' => 'b', 'ÿ' => 'y');

		$res = strtr($str, $unwanted_array);

		return $res;
	}

	/**
	 * sprintf like function
	 * 	Usage : 
	 * 		fromArgs("My string first arg: ?,  second arg: ?
	 * 		and again first: {1},  again second: {2}", "arg1", "arg2")
	 */
	public static function fromArgs($str, $args) {
		$params = array_slice(func_get_args(), 1);

		return static::fromArgsArray($str, $params);
	}

	/**
	 * Same as fromArgs but args are an array and it allow user to choose chars that will be replaced
	 * String params must be alpha numeric (and _ -)
	 * It is possible to escape $currentParamStr and $indexStrStart with a \ before
	 */
	public static function fromArgsArray($str, array $params, $currentParamStr = '?', $indexStrStart = '{', $indexStrEnd = '}') {
		$regex = '#(?<!\\\\)' . preg_quote($indexStrStart) . '([a-z0-9_-]+)' . preg_quote($indexStrEnd) . '#i';

		$replaces = preg_replace_callback($regex, function($matches) use($params) {
					$index = $matches[1];
					// var_dump($matches);
					if (!isset($params[$index])) {
						throw new \Exception('Parameter "' . S::escapeXss($index) . '" is not set');
					}
					else {
						return $params[$index];
					}
				}, $str);

		$replaces = str_replace('\\' . $indexStrStart, $indexStrStart, $replaces);

		$currentArg = 0;
		$pos = 0;

		$unamedParams = UArray::filterByKeyType($params);
		$nbUnamedParams = count($unamedParams);
		$strLen = strlen($replaces);

		while ($pos < $strLen AND ($pos = strpos($replaces, $currentParamStr, $pos)) !== false AND $currentArg < $nbUnamedParams) {
			if ($pos == 0 OR $replaces{$pos - 1} != '\\') {
				$arg = $unamedParams[$currentArg++];

				$replaces = substr_replace($replaces, $arg, $pos, strlen($currentParamStr));
				$strLen = strlen($replaces);
			}

			$pos++; // Pass ahead last "?"
		}

		$replaces = str_replace('\\' . $currentParamStr, $currentParamStr, $replaces);

		return $replaces;
	}

	/**
	 * Return $str if it is not empty or $or
	 */
	public static function ifNotEmpty($str, $or = '', $strPrefix = '', $strSuffix = '') {
		if ($str == '') {
			return $or;
		}
		else {
			return $strPrefix . $str . $strSuffix;
		}
	}

	public static function ifEmpty($str, $or = '', $strPrefix = '', $strPrefix = '') {
		return call_user_func_array('static::ifNotEmpty', func_get_args());
	}

	/**
	 * Return $prefix.$str if $str is not empty or $or if it is
	 */
	public static function prefixIfNotEmpty($str, $prefix, $or = '') {
		if ($str == '') {
			return $or;
		}
		else {
			return $prefix . $str;
		}
	}

	/**
	 * Return $str.$suffix if $str is not empty or $or if it is
	 */
	public static function suffixIfNotEmpty($str, $suffix, $or = '') {
		if ($str == '') {
			return $or;
		}
		else {
			return $str . $suffix;
		}
	}

	/**
	 * Check if $str begins with $beginStr and return (if success)
	 * the string that follow $beginStr in $nextStr
	 */
	public static function beginsWith($str, $beginStr, &$nextStr = null) {
		$beginStrLen = strlen($beginStr);

		$realBegin = substr($str, 0, $beginStrLen);

		if ($realBegin == $beginStr) {
			$nextStr = substr($str, $beginStrLen);

			return true;
		}
		else {
			return false;
		}
	}

	/**
	 * Check if $str ends with $endStr and return (if success)
	 * the string that precede $endStr in $precedeStr
	 */
	public static function endsWith($str, $endStr, &$precedeStr = null) {
		$endStrlen = strlen($endStr);

		$realEnd = substr($str, -$endStrlen);

		if ($realEnd == $endStr) {
			$precedeStr = substr($str, 0, -$endStrlen);

			return true;
		}
		else {
			return false;
		}
	}

	/**
	 * Join not empty ('') args with $separator
	 * ex: join(', ', $arg1, $arg2)
	 */
	public static function join($separator) {
		if (count(func_get_args()) > 1) {
			$args = array_slice(func_get_args(), 1);
			$arr = array();

			foreach ($args as $arg) {
				if ($arg != '') {
					$arr[] = $arg;
				}
			}
			return implode($separator, $arr);
		}
		else {
			return '';
		}
	}

	/**
	 * Convert true to 'true' and false to 'false'
	 */
	public static function boolToString($bool) {
		if ($bool) {
			return 'true';
		}
		else {
			return 'false';
		}
	}

	/**
	 * Append the string $appendOrEqual to $str or return it if $str is empty
	 * @param string $str
	 * @param string $appendOrEqual 
	 * @param string $separator separator between two string when then are combined
	 * @param bool $prepend Prepend instead of append
	 */
	public static function appendOrEqual($str, $appendOrEqual, $separator = '', $prepend = false) {
		$ret = $appendOrEqual;

		if ($str != '') {
			if (!$prepend) {
				$ret = $str . $separator . $appendOrEqual;
			}
			else {
				$ret = $appendOrEqual . $separator . $str;
			}
		}

		return $ret;
	}

	// Files

	/**
	 * Join part of a path.
	 * Ex: joinPath('here', 'is', 'my', 'path')
	 * Result in: here/is/my/path where "/" is replaced with
	 * the static::DIRECTORY_SEPARATOR const
	 */
	public static function joinPath() {
		$args = func_get_args();
		$path = implode(static::DIRECTORY_SEPARATOR, array_map('trim', $args));

		return $path;
	}

	/**
	 * Similar to explode('/', $path) or explode('\\', $path)
	 * but choose the correct one to avoid security problem
	 * ex: $fn = 'a.b/..\\myfile.txt';
	 * If you split with '/' and then test the innexistance of '..', 
	 * it will be true but on windows the '\' will be considered
	 */
	public static function splitPath($path) {
		if (static::DIRECTORY_SEPARATOR == '/') {
			$otherSeparator = '\\';
		}
		else {
			$otherSeparator = '/';
		}

		$secPath = str_replace($path, $otherSeparator, static::DIRECTORY_SEPARATOR);
		$splitPath = explode(static::DIRECTORY_SEPARATOR, $path);

		return $splitPath;
	}

	/**
	 * @deprecated Use Validator function instead
	 */
	public static function isDownPath($path) {
		// $parts = preg_split('#[\\\\/]+#', $path);
		$parts = static::splitPath($path);
		$level = 0;
		foreach ($parts as $part) {
			if ('..' === $part) {
				--$level;
			}
			elseif ('.' !== $part) {
				++$level;
			}

			if ($level < 0) {
				return false;
			}
		}

		return true;
	}

	/**
	 * Return the filename without extension from a path
	 */
	public static function getFileName($path) {
		$slashPos = strrpos($path, static::DIRECTORY_SEPARATOR);

		if ($slashPos === false) {
			$slashPos = 0;
		}
		else {
			$slashPos++;
		}

		return substr($path, $slashPos, strrpos($path, '.') - $slashPos
		);
	}

	/**
	 * Return the filename with extension from a path
	 */
	public static function getFullFileName($path) {
		return substr($path, strrpos($path, '/') + 1
		);
	}

	public static function getFileExtension($path, $toLower = true) {
		$ext = substr($path, strrpos($path, '.') + 1);

		if ($toLower) {
			return strtolower($ext);
		}
		else {
			return $ext;
		}
	}

	/**
	 * Return the full dir's name from a path (like a/b/e from a/b/c/e/f.png
	 * @param type $path
	 * @return type 
	 */
	public static function getFullDirName($path) {
		return substr($path, 0, strrpos($path, static::DIRECTORY_SEPARATOR)
		);
	}

	/**
	 * Change the file extension of a file path / file name
	 */
	public static function changeFileExtension($path, $newExt) {
		$extPos = strrpos($path, '.') + 1;

		$newPath = substr_replace($path, $newExt, $extPos);

		return $newPath;
	}

	/**
	 * @deprecated Use Validator function instead
	 */
	public static function isFileName($fn) {
		$regex = '#^[\\(\\).a-z0-9_-]+\.[\\(\\)a-z0-9_-]+$#iD';

		return (bool) preg_match($regex, $fn);
	}

	/**
	 * @deprecated Use removeLast instead
	 * Remove the last character of $str if it is $char
	 */
	public static function removeLastChar($str, $char) {
		if (strrpos($str, $char) == strlen($str) - 1) {
			$str = substr($str, 0, -1);
		}

		return $str;
	}

	/**
	 * Same as strpos, but with caseSensitive and start from params
	 */
	public static function getPos($str, $toSearch, $offset = 0, $stopAtFirst = true, $caseSensitive = true) {
		if ($stopAtFirst) {
			if ($caseSensitive) {
				$func = 'strpos';
			}
			else {
				$func = 'stripos';
			}
		}
		else {
			if ($caseSensitive) {
				$func = 'strrpos';
			}
			else {
				$func = 'strripos';
			}
		}

		$pos = call_user_func($func, $str, $toSearch, $offset);

		return $pos;
	}

	/**
	 * Return the position of the $occurenceIndex occurence of $toSearch in $str
	 * @param string $str
	 * @param string $toSearch
	 * @param integer $occurenceIndex 0-based occurence index to look after.
	 * (0 for first, static::POS_END for last, or any other number)
	 * @param bool $caseSensitive
	 * @return false|integer
	 */
	public static function getPosByOccurenceIndex($str, $toSearch, $occurenceIndex, $caseSensitive = true) {
		$pos = static::getPos($str, $toSearch, 0, true, $caseSensitive);
		$lastPos = false;

		for ($currOcc = 0; $pos !== false AND ($occurenceIndex == static::POS_END OR $currOcc < $occurenceIndex); $currOcc++) {
			$lastPos = $pos;
			$pos = static::getPos($str, $toSearch, $pos + 1, true, $caseSensitive);
		}

		if ($occurenceIndex == static::POS_END AND $lastPos !== false) {
			return $lastPos;
		}
		elseif ($pos !== false) {
			return $pos;
		}
		else {
			return false;
		}
	}

	/**
	 * Remove $firstStr of $str, if $firstStr is at the begining of $str (like "hihi" in "hihiabcd")
	 */
	public static function removeFirst($str, $firstStr) {
		$strBegining = substr($str, 0, strlen($firstStr));

		if ($strBegining == $firstStr) {
			$str = $str = substr($str, strlen($firstStr));
		}

		return $str;
	}

	/**
	 * Remove the first occurence of $toSearch in $str and return the result
	 */
	public static function removeFirstOccurence($str, $toSearch, $caseSensitive = true) {
		$pos = static::getPos($str, $toSearch, 0, true, $caseSensitive);

		if ($pos !== false) {
			$str = substr_replace($str, '', $pos, strlen($toSearch));
		}

		return $str;
	}

	/**
	 * Remove the last occurence of $toSearch in $str and return the result
	 */
	public static function removeLastOccurence($str, $toSearch, $caseSensitive = true) {
		$pos = static::getPos($str, $toSearch, 0, false, $caseSensitive);

		if ($pos !== false) {
			$str = substr_replace($str, '', $pos, strlen($toSearch));
		}

		return $str;
	}

	/**
	 * Remove $lastStr of $str, if $lastStr is at the end of $str (like "hihi" in "abcdhihi")
	 */
	public static function removeLast($str, $lastStr) {
		$strEnd = substr($str, -strlen($lastStr));

		if ($strEnd == $lastStr) {
			$str = $str = substr($str, 0, -strlen($lastStr));
		}

		return $str;
	}

	/**
	 * Return the string which is before $toSearch in $str
	 * @param type $str
	 * @param type $toSearch
	 * @param type $stopAtOccurenceIndex Method will return the string before
	 * the $stopAtOccurence of $toSearch
	 * @param type $caseSensitive
	 * @return string
	 */
	public static function getBefore($str, $toSearch, $stopAtOccurenceIndex = 0, $caseSensitive = true) {
		$pos = static::getPosByOccurenceIndex($str, $toSearch, $stopAtOccurenceIndex, $caseSensitive);

		if ($pos === false) {
			return '';
		}
		else {
			$ret = substr($str, 0, $pos);

			return $ret;
		}
	}

	/**
	 * Return the string which is before $toSearch in $str
	 * @param type $str
	 * @param type $toSearch
	 * @param type $stopAtOccurenceIndex Method will return the rest of the string 
	 * after the $stopAtOccurence of $toSearch
	 * @param type $caseSensitive
	 * @return string
	 */
	public static function getAfter($str, $toSearch, $stopAtOccurenceIndex = 0, $caseSensitive = true) {
		$pos = static::getPosByOccurenceIndex($str, $toSearch, $stopAtOccurenceIndex, $caseSensitive);

		if ($pos === false) {
			return '';
		}
		else {
			$ret = substr($str, $pos + strlen($toSearch));

			return $ret;
		}
	}

	// HTML

	/**
	 * Make an HTML (XML) attribute string from an array
	 * Null values are not written
	 * 
	 * @param bool $addSpaceAtStartIfNotEmpty If true, add a space (' ') at the beginning of the returned string if it is not empty
	 * @return string The attribute string with a " " at the end or "" if array is empty
	 */
	public static function implodeHtmlAttributes($arr, $addSpaceAtStartIfNotEmpty = false) {
		if (count($arr)) {
			$str = ' ';

			foreach ($arr as $k => $v) {
				if (is_array($v)) {
					throw new Exception('Attribute value cannot be an array (' . print_r($arr, true) . ')');
				}
				elseif (!is_null($v)) {
					settype($v, 'string');

					$str .= $k . '="' . Security\Security::escapeSpecialChars($v) . '" ';
				}
			}

			if ($addSpaceAtStartIfNotEmpty) {
				$str = ' ' . $str;
			}

			return substr($str, 0, -1);
		}
		else {
			return '';
		}
	}

	// JAVASCRIPT

	public static function jQuerySelectorEsc($selector) {
		$selector = preg_replace('/([ #;&,.+*~\':"!^$[\]()=>|\/])/', '\\\\$1', $selector);

		return $selector;
	}

	/**
	 * Same as json_encode but encode correctly js functions and other javascript code that begin with $jsPrefix OR 'function()'
	 * @deprecated Use JsonNode class instead
	 */
	public static function jsonEncodeFunc($input, $jsonEncodeOptions = 0, $jsPrefix = '-JS_NOT_ENCODE-') {
		$recFunc = function($input, $jsonEncodeOptions, $jsPrefix, $funcs = array(), $level = 0) use (&$recFunc) {
					foreach ($input as $key => $value) {
						if (is_array($value)) {
							$ret = $recFunc($value, $jsonEncodeOptions, $jsPrefix, $funcs, 1);
							$input[$key] = $ret[0];
							$funcs = $ret[1];
						}
						else {
							if (substr($value, 0, 10) == 'function()') {
								$func_key = "#" . uniqid() . "#";
								$funcs[$func_key] = $value;
								$input[$key] = $func_key;
							}
							elseif (UString::beginsWith($value, $jsPrefix, $nextStr)) {
								$func_key = "#" . uniqid() . "#";
								$funcs[$func_key] = $nextStr;
								$input[$key] = $func_key;
							}
						}
					}

					if ($level == 1) {
						return array($input, $funcs);
					}
					else {
						$input_json = json_encode($input);
						// $input_json = json_encode($input, JSON_FORCE_OBJECT);

						foreach ($funcs as $key => $value) {
							$input_json = str_replace('"' . $key . '"', $value, $input_json);
						}
						return $input_json;
					}
				};

		return $recFunc($input, $jsonEncodeOptions, $jsPrefix);
	}

	public static function equals($str1, $str2, $caseSensitive = true) {
		if ($caseSensitive) {
			return strcmp($str1, $str2);
		}
		else {
			return strcasecmp($str1, $str2);
		}
	}

	/**
	 * Translate object notation like a.b.c.d to array notation like a[b][c][d]
	 * 
	 * @param array|string $keys The keys : a string in which keys are separated 
	 * with $stringSeparator or an array of keys
	 * @param type $stringSeparator
	 * @return string
	 */
//		public static function getArrayNotation($keys, $stringSeparator = '.') {
//			if (is_string($keys)) {
//				$arr = explode($stringSeparator, $keys);
//			}
//			
//			if (UArray::isNotEmptyArray($arr)) {
//				$firstKey = array_shift($array);
//				
//				$res = $firstKey;
//				
//				foreach ($arr as $value) {
//					$res .= '['.$value.']';
//				}
//				
//				return $res;
//			}
//			else {
//				return '';
//			}
//		}
//		
	/**
	 * @param string $htmlPath A string like "a[b][c]"
	 * @return string The result as a.b.c
	 */
	public static function getObjectNotationFromHtmlPath($htmlPath) {
		$keys = UArray::getArrayKeysFromHtmlPath($htmlPath);

		$objectNotation = UArray::getObjectNotationString($keys);

		return $objectNotation;
	}

	/**
	 * Return the object notation array keys from object notation string (or array)
	 * @param string|array $objectNotation a.b.c or array('a', 'b', 'c')
	 * @return array Ex: array('a', 'b', 'c')
	 */
	public static function getObjectNotationArray($objectNotation) {
		if (is_array($objectNotation)) {
			return $objectNotation;
		}
		else {
			return explode('.', $objectNotation);
		}
	}

	public static function mergeObjectNotations($objectNotation1, $objectNotation2) {
		return $$objectNotation1 . '.' . $objectNotation2;
	}

}

?>